/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
  <file> uls_num.c </file>
  <brief>
    Recognizing the number tokens from input.
    This file is part of ULS, Unified Lexical Scheme.
  </brief>
  <author>
    Stanley J. Hong <babel96@gmail.com>, May 2011.
  </author>
*/

#include "common.h"
#include "uls_num.h"
#include "uls_lex.h"
#include "uls_log.h"

static uls_char_t
hexa_num_to_char(unsigned int num)
{
	if (num < 10) return '0' + num;

	return 'A' + (num - 10);
}

static uls_char_t
get_hexa_char_from_octal(unsigned int* p_two_octals, int a_bits)
{
	// a_bits = 1, 2, 3 bits
	int n, b_bits; // a_bits + b_bits == 4;
	unsigned int mask, h_val;

	b_bits = 4 - a_bits;

	//  (1)   aaa b??   a_bits=3, b_bits=1, n=2
	//  (2)   ?aa bb?   a_bits=2, b_bits=2, n=1
	//  (3)   ??a bbb   a_bits=1, b_bits=3, n=0

	n = 3 - b_bits;
	mask = ((1 << 4) - 1) << n; // will cover {a, b}

	h_val = (*p_two_octals & mask) >> n; // hval == a*b*
	*p_two_octals &= (1 << n) - 1; // remain only the right n-bits

	return hexa_num_to_char(h_val);
}

int
uls_oct2hex_str(uls_char_t** p_lptr, uls_char_t *lptr_end, uls_char_t* outbuf)
{
	uls_char_t  *lptr=*p_lptr;
	uls_char_t ch, *ptr2;
	unsigned int o_num;
	int  a_bits, b_bits;

	for ( ; ; lptr++) {
		if (lptr == lptr_end) { /* trivial case */
			if (lptr == *p_lptr)
				return 0;

			/* trivial case */
			*outbuf++ = '0';
			*outbuf = '\0';
			*p_lptr = lptr;

			return 1;
		}
		if (*lptr != '0') break;
	}

	if (is_octal_char(*lptr) && (lptr+1)==lptr_end) {
		*outbuf++ = *lptr++;
		*outbuf = '\0';
		*p_lptr = lptr;
		return 1;
	}

	b_bits = (3 * (int) ((lptr_end-lptr) & 0x03)) % 4;

	o_num = *lptr++ - '0';
	ptr2 = outbuf;

	if (b_bits > 0) {
		a_bits = 4 - b_bits;

		ch = get_hexa_char_from_octal(&o_num, a_bits);
		if (ch != '0') *ptr2++ = ch;

		a_bits = 3 - b_bits;
		if (a_bits == 0) {
			o_num = *lptr++ - '0';
			a_bits = 3;
		}
	} else {
		a_bits = 3;
	}

	/* o_num , a_bits{1,2,3} */
	/* Notice that the a_bits in the right of o_num, ... */
	while (1) {
		b_bits = 4 - a_bits;
		o_num = (o_num << 3) | (*lptr++ - '0');

		ch = get_hexa_char_from_octal(&o_num, a_bits);
		*ptr2++ = ch;

		a_bits = 3 - b_bits;
		if (a_bits == 0) {
			if (lptr == lptr_end) break;
			o_num = *lptr++ - '0';
			a_bits = 3;
		}
	}

	*ptr2 = '\0';
	*p_lptr = lptr;

	return (int) (ptr2 - outbuf);
}

static uls_char_t*
skip_hexadecimal(uls_char_t** p_lptr, int* p_len)
{
	uls_char_t *lptr0 = *p_lptr, *lptr;
	uls_char_t ch;

	for (lptr=lptr0; ; lptr++) {
		if ((ch=*lptr) != '0') {
			if (is_hexa_char(ch)) {
				break;
			} else {
				if (lptr0 == lptr) {
					return NULL;
				} else { // #-of-0's > 0
					*p_len = 1;
					*p_lptr = lptr;
					return lptr0;
				}
			}
		}
	}

	lptr0 = lptr++;

	for ( ; ; lptr++) {
		if (!is_hexa_char(*lptr))
			break;
	}

	*p_len = (int) (lptr - lptr0);
	*p_lptr = lptr;

	return lptr0;
}

#define ZERO_RET() do { \
	*p_flags |= ULS_NUM_FL_ZERO; \
	*p_flags &= ~ULS_NUM_FL_MINUS; \
	*p_n1 = 1; \
	*p_lptr = lptr; \
	return (uls_char_t *) "0"; \
} while (0)

#define ERR_RET() do { \
	*p_flags |= ULS_NUM_FL_ERROR; \
	return NULL; \
} while (0)

uls_char_t*
num2stdfmt_0(uls_char_t** p_lptr,
	int* p_flags, int *p_n1, uls_char_t** p_outbuf, int* p_outbuf_siz)
{
	uls_char_t *lptr=*p_lptr, *lptr0, *lptr1;
	uls_char_t* lptr_end;
	uls_char_t *outbuf;
	uls_char_t ch;
	int len;

	*p_flags = 0;

	if ((ch=*lptr) == '-') {
		*p_flags |= ULS_NUM_FL_MINUS;
		ch = *++lptr;
	}

	lptr0 = lptr;
	if (!(isdigit(ch) || (ch=='.' && isdigit(lptr[1])))) {
		ERR_RET();
	}

	// ch == 0, 1, ... 9, or '.'

	if (ch != '0') {
		return NULL; // No Error
	}

	if ((ch=*++lptr) == 'x') { // hexa-decimal
		++lptr;

		if ((lptr0=skip_hexadecimal(&lptr, p_n1)) == NULL) {
			--lptr;
			ZERO_RET();
		}

		*p_flags |= ULS_NUM_FL_HEXA;
		*p_lptr = lptr;

	} else if (isdigit(ch)) { // octal, transform it to hexa.
		// calc the memory siz 'p_outbuf_siz'
		for ( ; ; lptr++) {
			if (*lptr != '0')
				break;
		}

		if (*lptr == '.') {
			*p_lptr = lptr;
			return NULL;
		}

		lptr1 = lptr;
		for ( ; ; lptr++) {
			if (!is_octal_char(*lptr))
				break;
		}

		if ((len = (int) (lptr - lptr1)) == 0) {
			ZERO_RET();
		}

		lptr_end = lptr1 + len;
		lptr = lptr1;

		// realloc *p_outbuf as needed
		str_append(p_outbuf, p_outbuf_siz, 0, NULL, len);
		outbuf = *p_outbuf;
		*p_flags |= ULS_NUM_FL_TOKBUF_USED;

		len = uls_oct2hex_str(&lptr, lptr_end, outbuf);

		*p_flags |= ULS_NUM_FL_HEXA;
		*p_n1 = len;

		lptr0 = outbuf;
		*p_lptr = lptr;

	} else if (ch == '.') { // 0.
		// *p_n1 = 0;
		lptr0 = NULL;

	} else {
		ZERO_RET();
	}

	return lptr0;
}

static int
get_surplus_exponent(uls_char_t** p_lptr)
{
	uls_char_t *lptr = *p_lptr;
	int m;

	// .50417074
	// .00050417094
	// find first the non-zero digit
	for ( ; ; lptr++) {
		if (*lptr != '0') {
			// m==3 in  .00050417094 : # of 0' below '.'
			m = (int) (lptr - *p_lptr); // # of '0's
			break;
		}
	}

	*p_lptr = lptr;
	return m;
}

static int
check_expo(uls_char_t** p_lptr, int* p_expo)
{
	uls_char_t *lptr0=*p_lptr, *lptr;
	uls_char_t  ch;
	int expo, minus = 0;

	lptr = lptr0;
	if (*lptr != 'E' && *lptr != 'e') {
		return 0;
	}

	ch = *++lptr;

	if (ch == '-') {
		++lptr;
		minus = 1;
	} else if (ch=='+') {
		++lptr;
	}

	lptr0 = lptr;
	for (expo=0; ; lptr++) {
		if (!isdigit(ch=*lptr)) {
			if (lptr0 == lptr) {
				return 0;
			}
			break;
		}
		expo = 10 * expo + (ch - '0');
	}

	if (minus) expo = -expo;
	*p_expo = expo;

	*p_lptr = lptr;
	return 1;
}

uls_char_t*
num2stdfmt(uls_char_t** p_lptr,
	int* p_flags, int *p_n1, int* p_n2, int* p_expo)
{
	uls_char_t *lptr=*p_lptr, *lptr0, *lptr1;
	int  n, n1, n2, m;
	uls_char_t ch;

	*p_flags = 0;
	*p_n2 = 0;

	if ((ch=*lptr) == '-') {
		*p_flags |= ULS_NUM_FL_MINUS;

		if ((ch=*++lptr) != '.' && !isdigit(ch)) {
			ERR_RET();
		}
		// ch == 0, 1, ... 9, or '.'
	}

	lptr0 = lptr; // next to '-'
	if (ch == '.' || (ch == '0' && lptr[1] == '.')) {
		if (ch == '0') lptr0 = ++lptr;

		*p_flags |= ULS_NUM_FL_FLOAT;

		++lptr; // next to '.'
		n = get_surplus_exponent(&lptr);

		if (!isdigit(*lptr)) {
			ZERO_RET();
		}

		if (n > 0) n = -n;

		n1 = 0;
		lptr1 = lptr0 = lptr;

	} else if (ch == '0') {
		ZERO_RET();

	} else if (isdigit(ch)) {
		for ( ; ; lptr++) {
			if (!isdigit(*lptr)) {
				// integer
				break;
			}
		}

		if (*lptr != '.') {
			*p_n1 = (int) (lptr - lptr0);
			*p_n2 = 0;
			*p_expo = 0;
			*p_lptr = lptr;
			return lptr0;
		}

		*p_flags |= ULS_NUM_FL_FLOAT;
		n1 = (int) (lptr - lptr0);

		lptr1 = lptr; // *lptr1 == '.'
		n = 0;

		for (++lptr; ; lptr++) { // find 0's forward
			if (!isdigit(*lptr)) {
				*p_lptr = lptr;

				for (lptr=lptr1-1; *lptr=='0'; --lptr) {
					// NOTHING
				}
				++lptr;

				n = (int) (lptr1 - lptr);
				n1 -= n;

				*p_n1 = n1;
				*p_n2 = 0;
				*p_expo = n;

				return lptr0;

			} else if (*lptr != '0') {
				break;
			}
		}

		++lptr1;
	} else {
		// TESTCASE-2:
		ERR_RET();
	}

	for ( ; ; lptr++) {
		if (!isdigit(*lptr)) {
			break;
		}
	}

	n2 = (int) (lptr - lptr1);

	if (check_expo(&lptr, &m)) {
		n += m;
	}

	*p_n1 = n1;
	*p_n2 = n2;
	*p_expo = n;

	*p_lptr = lptr;

	return lptr0;
}

static int 
div_decstr_by_16(uls_char_t** p_wrd, int* p_remainder)
{
	// INPUT: wrd(ascii-string): number, 50417094
	// wrd   : wrd[0]=='5', wrd[1]=='0', wrd[2]=='4', ..., wrd[7]=='4', wrd[8]=='\0'
	// wrd/16 : wrd[1]==3', wrd[2]=='1', wrd[3]=='5', ..., wrd[7]=='8', wrd[8]=='\0'
	// ptr0, the return-pointer: &wrd[1]

	uls_char_t *wrd, *ptr, *ptr0;
	int a, carry;

	wrd = *p_wrd;

	if ((a=wrd[0]-'0') == 0 || wrd[1]=='\0') { // wrd == "0"
		wrd[0] = '0'; wrd[1] = '\0';
		*p_wrd = wrd; 
		*p_remainder = a;
		return 1;
	}

	a = 10 * a + (wrd[1] - '0');
	if (a < 16 && wrd[2]=='\0') {
		wrd[1] = '0';
		*p_wrd = wrd + 1; 
		*p_remainder = a;
		return 1;
	}

	if (a < 16) {
		ptr = ptr0 = wrd + 2;
		carry = a;
	} else {
		ptr = ptr0 = wrd + 1;
		*ptr++ = '0' + a / 16;
		carry = a % 16;
	}

	for ( ; *ptr!='\0'; ptr++) {
		a = 10*carry + (*ptr - '0');
		*ptr = '0' + a / 16;
		carry = a % 16;
	}

	*p_wrd = ptr0; 
	*p_remainder = carry;

	return 0;
}

static int
decstr2hexbin_reversed(uls_char_t* wrd)
{
	// wrd(IN-OUT-PARAM): decimal string
	// return-value: # of hexa digits(i.e., # of nibbles)
	// Let a == wrd, and the input string wrd[] be "50FCD".
	// Then, the output a[] will be 
	//   a[0] == 0xCD, a[1] == 0x0F, a[2] == 0x05, 
	//   an the return-value 3.

	uls_char_t *ptr;
	uls_char_t *ptr2, ch1, ch2;
	int  is_zero, carry, n = 0;

	ptr = wrd;
	ptr2 = wrd;

	do {
		is_zero = div_decstr_by_16(&ptr, &carry);
		ch2 = (uls_char_t) carry;

		if (is_zero) {
			*ptr2++ = ch2;
			n += 1; // 1-nibbles added!
			break;
		}

		is_zero = div_decstr_by_16(&ptr, &carry);
		ch1 = (uls_char_t) carry;

		*ptr2++ = (ch1<<4) | ch2;
		n += 2; // 2-nibbles added!
	} while (!is_zero);

	*ptr2 = '\0';
	return n;
}

int
uls_dec2hex_str(uls_char_t* wrd, uls_char_t* outbuf)
{
	uls_char_t ch;
	int  n_nibbles, n_bytes, i, k;
	int  wrd_len;

	wrd_len = uls_strlen(wrd);

	n_nibbles = decstr2hexbin_reversed(wrd);

	// Let wrd == 4F02, n_nibbles==3
	n_bytes =  (n_nibbles / 2) + (n_nibbles % 2);

	// Now, wrd == 024F. We wanna the string "24F".
	k = n_nibbles;
	outbuf[k] = '\0';

	for (i=0; i < n_nibbles/2; i++) {
		ch = wrd[i];
		outbuf[--k] = hexa_num_to_char(ch & 0x0F);
		outbuf[--k] = hexa_num_to_char(ch >> 4);
	}

	if (k > 0) {
		ch = wrd[i];
		outbuf[--k] = hexa_num_to_char(ch & 0x0F);
	}
	// assert: k == 0

	return n_nibbles; 
}
